首先,先講一下昨天的錯誤拉
昨天的錯誤碼找到拉~
改了一下useLocalStorage.ts的readInitValue
發現如果不是JSON.stringify()設置的會有問題
改了一下改成判斷能不能被JSON.parse解析
const readInitValue = useCallback(() => {
try {
const item = localStorage.getItem(key);
if (!item) {
return initialValue;
}
try {
// 嘗試把item變成JSON解析
return JSON.parse(item);
} catch (e) {
// 如果只是純字串則回傳
return item;
}
} catch (err) {
console.error("Error reading from localStorage:", err);
return initialValue;
}
}, [key, initialValue]);
-------------------------------分隔線-------------------------------------
今天要用第二種方法來寫了
這個方法的重點是不在乎最後的結果(不在乎最後的儲存的store)
而是要去追蹤是否真的有正確call 這個setItem function
裡面參數與這個行為是不是正確的
// Step 1: 製作Mock sessionStorage 模擬 sessionStorage的行為
// `getItem`: 用于模拟从 sessionStorage 中读取数据的行为。
// `setItem`: 用于模拟向 sessionStorage 中写入数据的行为。
// `clear`: 用于模拟清空 sessionStorage 的行为。
const mockSessionStorage = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn(),
};
jest.fn()
是 Jest 測試框架提供的一個方法,用於創建模擬(Mock)函數。模擬函數允許你追踪它們如何被調用、被調用多少次、什麼參數被傳遞之類的。
首先我們跟昨天不一樣的是
我們這個測試只關心 localStorage
方法(如 getItem、setItem
等)是否被正確地調用,以及它們是否被傳遞了正確的參數,這個就是使用 Jest Mock 函數的時機。這種方式可以讓我們很容易地追蹤函數的調用狀況和傳遞的參數
接下來跟昨天一樣,將mockSessionStorage
替換真實的sessionStorage
Object.defineProperty(global, "sessionStorage", { value: mockSessionStorage });
我們這次測試寫的跟昨天不同,我們寫一點其他還可以測試的東西,這樣比較有趣,比如發生error時或getItem取不到的時候~
1.測試一開始sessionStorage就有初始值
2.測試確保sessionStorage可以設置值進去
3.測試確保如果 sessionStorage 中的值不是一個 JSON 字串,hook 依然能夠正確地讀取它。
4.測試確保即使 sessionStorage 拋出一個錯誤(比如由於存儲已滿或其他原因),hook 還是能夠正確地設置初始值。
5.測試確保在 sessionStorage 為空(或者指定的 key 不存在)的情況下,hook 能正確地設置初始值。
6.測試確保如果你傳入一個函數來更新存儲的值,這個函數能正確地接收到舊值作為參數,並正確地更新新值。
describe("useSessionStorage custom hook", () => {
// 昨天有提到afterEach在做什麼,忘記的記得看昨天的喔
// 每次it測試完清除模擬函數的調用信息
afterEach(() => {
(sessionStorage.getItem as jest.Mock).mockClear();
(sessionStorage.setItem as jest.Mock).mockClear();
});
// 測試一開始sessionStorage就有初始值
it("gets initial value from sessionStorage", () => {
(sessionStorage.getItem as jest.Mock).mockReturnValueOnce('"someValue"');
const { result } = renderHook(() => useSessionStorage("key", "default"));
expect(result.current[0]).toBe("someValue");
});
//測試確保可以設置值進去
it("sets value to sessionStorage", () => {
const { result } = renderHook(() => useSessionStorage("key", "default"));
act(() => {
result.current[1]("newValue"); // 調用 setValue 函數
});
expect(sessionStorage.setItem).toHaveBeenCalledWith("key", '"newValue"');
});
it("should handle non-JSON values gracefully", () => {
mockSessionStorage.getItem.mockReturnValueOnce("non-json-value");
const { result } = renderHook(() => useSessionStorage("key", "default"));
expect(result.current[0]).toBe("non-json-value");
});
it("should handle errors thrown by sessionStorage gracefully", () => {
mockSessionStorage.getItem.mockImplementationOnce(() => {
throw new Error("Some error");
});
const { result } = renderHook(() => useSessionStorage("key", "default"));
expect(result.current[0]).toBe("default");
});
it("should set initial value if sessionStorage is empty", () => {
mockSessionStorage.getItem.mockReturnValueOnce(null);
const { result } = renderHook(() => useSessionStorage("key", "default"));
expect(result.current[0]).toBe("default");
});
it("should handle function updater correctly", () => {
const { result } = renderHook(() => useSessionStorage("key", 0));
act(() => {
result.current[1]((prev) => prev + 1);
});
expect(mockSessionStorage.setItem).toHaveBeenCalledWith(
"key",
JSON.stringify(1)
);
});
// 更多測試用例...
});
看完會不會噗撒撒,看不懂在幹嘛,其實我覺得jest有一個很好的地方,他的function英文其實就在解釋他的動作,
mockReturnValueOnce
是指他會回傳一次你所傳的參數,如果你在call 一次則會回傳undefined,所以在我們useSession有呼叫getItem的地方,他第一次就會回傳'"someValue"'
(sessionStorage.getItem as jest.Mock).mockReturnValueOnce('"someValue"')
toHaveBeenCalledWith
用於檢查一個模擬函數(Mock Function)是否有被帶有特定參數調用過
這裡就是我們期望sessionStorage.setItem("key", '"newValue"')有被呼叫
expect(sessionStorage.setItem).toHaveBeenCalledWith("key", '"newValue"')
如果你需要對你的custom hook
(例如:localStorage
) 方法的內部行為要進行更細致的控制,使用昨天那種自定義的模擬類可能會更有用。
如果你只是想簡單地驗證方法是否被調用,以及它們是如何被調用的,則使用 jest.fn() 應該就足夠了
忘記說第三個方法,就是用jest-localstorage-mock
套件拉,我考慮了一下,決定不講解這個套件了,這種只針對單樣東西的就自己可以再研究看看,就不特別用一篇講解了
完整程式碼
import { renderHook, act } from "@testing-library/react";
import { useSessionStorage } from "../src"; // 請對應您的文件結構
// Step 1: 製作Mock sessionStorage 模擬 sessionStorage的行為
// `getItem`: 用于模拟从 sessionStorage 中读取数据的行为。
// `setItem`: 用于模拟向 sessionStorage 中写入数据的行为。
// `clear`: 用于模拟清空 sessionStorage 的行为。
const mockSessionStorage = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn(),
};
Object.defineProperty(global, "sessionStorage", { value: mockSessionStorage });
describe("useSessionStorage custom hook", () => {
// 清除模擬函數的調用信息
afterEach(() => {
(sessionStorage.getItem as jest.Mock).mockClear();
(sessionStorage.setItem as jest.Mock).mockClear();
});
// 測試一開始sessionStorage就有初始值
it("gets initial value from sessionStorage", () => {
(sessionStorage.getItem as jest.Mock).mockReturnValueOnce('"someValue"');
const { result } = renderHook(() => useSessionStorage("key", "default"));
expect(result.current[0]).toBe("someValue");
});
//測試確保可以設置值進去
it("sets value to sessionStorage", () => {
const { result } = renderHook(() => useSessionStorage("key", "default"));
act(() => {
result.current[1]("newValue"); // 調用 setValue 函數
});
expect(sessionStorage.setItem).toHaveBeenCalledWith("key", '"newValue"');
});
//測試確保如果 sessionStorage 中的值不是一個 JSON 字串,hook 依然能夠正確地讀取它。
it("should handle non-JSON values gracefully", () => {
mockSessionStorage.getItem.mockReturnValueOnce("non-json-value");
const { result } = renderHook(() => useSessionStorage("key", "default"));
expect(result.current[0]).toBe("non-json-value");
});
// 測試確保即使 sessionStorage 拋出一個錯誤(比如由於存儲已滿或其他原因),hook 還是能夠正確地設置初始值
it("should handle errors thrown by sessionStorage gracefully", () => {
mockSessionStorage.getItem.mockImplementationOnce(() => {
throw new Error("Some error");
});
const { result } = renderHook(() => useSessionStorage("key", "default"));
expect(result.current[0]).toBe("default");
});
// 測試確保在 sessionStorage 為空(或者指定的 key 不存在)的情況下,hook 能正確地設置初始值
it("should set initial value if sessionStorage is empty", () => {
mockSessionStorage.getItem.mockReturnValueOnce(null);
const { result } = renderHook(() => useSessionStorage("key", "default"));
expect(result.current[0]).toBe("default");
});
// 測試確保如果你傳入一個函數來更新存儲的值,這個函數能正確地接收到舊值作為參數,並正確地更新新值
it("should handle function updater correctly", () => {
const { result } = renderHook(() => useSessionStorage("key", 0));
act(() => {
result.current[1]((prev) => prev + 1);
});
expect(mockSessionStorage.setItem).toHaveBeenCalledWith(
"key",
JSON.stringify(1)
);
});
// 更多測試用例...
});